package com.lz.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.lz.gmall.constant.GmallConstant;
import com.lz.gmall.gateway.properties.AuthUrlProperties;
import com.lz.gmall.user.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.util.Arrays;

import static jdk.nashorn.internal.runtime.GlobalFunctions.encodeURIComponent;

//全局过滤器
@Component
@EnableConfigurationProperties(AuthUrlProperties.class)
public class UserAuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private AuthUrlProperties authUrlProperties;

    //路径匹配器
    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    /**
     *拦截请求，让我们自定义请求的业务逻辑
     * @param exchange 封装了request对象和response对象
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();//获取发送的请求
        String path = request.getURI().getPath();

        //判断是否是静态资源，访问静态资源直接放行
        for (String noauth : authUrlProperties.getNoauths()) { //获取到数据：/img/**
            if (antPathMatcher.match(noauth,path)){ //判断路径是否满足规则 pattern模式，模板
                //判断是静态资源直接放行
                return chain.filter(exchange);
            }
        }

        //判断是否是受保护资源，是受保护资源，判断token决定放行
        for (String auth : authUrlProperties.getAuths()) {
            if (antPathMatcher.match(auth,path)){
                //是受保护的资源，判断token
                String token = getToken(exchange);
                if (StringUtils.isEmpty(token)){
                    // 没有token，表示没有登录，滚去登录页面,通过重定向原理进行重定向指定路径
                    return locationUrl(exchange);
                }
                //判断token的合法性,从redis中查询数据
                UserInfo userInfo = getUserInfo(token);
                if (userInfo==null){
                    // 没有token，表示没有登录，滚去登录页面，通过重定向原理进行重定向指定路径
                    return locationUrl(exchange);
                }
                //有token,并于redis查询到对应数据,放行
                return userIdThrought(exchange,chain,userInfo);//用于用户id的透传
            }
        }

        //普通请求，如果没有登录就直接放行，登录状态下就进行id透传
        String token = getToken(exchange);
        if (StringUtils.isEmpty(token)){
            //没用登录状态下的普通请求，要进行临时id的透传
            String userTempId = getUserTempId(exchange);
            //return chain.filter(exchange);
            return userTempIdThrought(exchange,chain,userTempId);
        }
        //登录状态下进行id透传，要先判断token合法性
        UserInfo userInfo = getUserInfo(token);
        if (userInfo==null){
            //伪造的令牌，让他回到登录页面，并删除token
            return locationUrl(exchange);
        }
        //该token合法，进行id透传
        //进行用户id透传的时候，考虑到购物车临时用户和登录用户商品的合并，也要传递临时id的透传
        return userIdThrought(exchange,chain,userInfo);
        //return chain.filter(exchange);//放行
    }

    /**
     * 进行放行，在放行的时候需要进行用户临时id的透传，思想就是：就是把用户的临时id放到请求头中
     * @param exchange 封装了请求和响应
     * @param chain 放行
     * @param userTempId 临时id
     * @return
     */
    private Mono<Void> userTempIdThrought(ServerWebExchange exchange, GatewayFilterChain chain, String userTempId) {
        //重新构造一个新的request请求
        //exchange.getRequest().getHeaders().add();使用这个方法会报错，因为exchagne封装的request对象是只读的，不能添加修改
        ServerHttpRequest tempIdRequest = exchange.getRequest().mutate().header("userTempId", userTempId).build();
        ServerWebExchange tempIdExchange = exchange.mutate().request(tempIdRequest).response(exchange.getResponse()).build();

        return chain.filter(tempIdExchange);
    }

    /**
     * 获取用户的临时id
     * 第一种：存放在rookie中的userTempId
     * 第二种：存放在请求头中的userTempId
     * 因为普通请求，没有采用前端axios发送，没有经过请求拦截器，请求头中是没有userTempId的，所以要获取cookie中的userTempId
     *
     * 临时id的产生，是由于前端点击加入购物车，前端生成的临时id（条件是没有token，并且没有userTempId）
     * 判断是否登录和是否存在临时用户，如果都没有，添加临时用户
     * @param exchange 封装了request和response对象
     * @return
     */
    private String getUserTempId(ServerWebExchange exchange) {
        //从cookie中获取
        HttpCookie httpCookie = exchange.getRequest().getCookies().getFirst("userTempId");
        if (httpCookie!=null){
            return httpCookie.getValue();
        }
        //从请求头中获取
        String userTempId = exchange.getRequest().getHeaders().getFirst("userTempId");
        if(!StringUtils.isEmpty(userTempId)){
            return userTempId;
        }

        return null;
    }


    /**
     * 进行放行，在放行的时候需要进行用户id的透传，思想就是：就是把用户的id放到请求头中
     * 进行用户id透传的时候，考虑到购物车临时用户和登录用户商品的合并，也要传递临时id的透传
     * @param exchange 封装request和response对象
     * @param chain 放行
     * @param userInfo 获取用户的id
     * @return
     */
    private Mono<Void> userIdThrought(ServerWebExchange exchange, GatewayFilterChain chain, UserInfo userInfo) {

        //获取到临时用户id
        String userTempId = getUserTempId(exchange);

        //构建一个新的请求，之前的请求是没有userId的
        // 构建一个新的request对象, exchange.getRequest().mutate()获取请求对象，然后调用mutate方法得到一个request的建造者对象
        ServerHttpRequest request = exchange.getRequest().mutate()
                .header("userId", String.valueOf(userInfo.getId()))
                .header("userTempId",userTempId)
                .build();

        // 构建一个新的exchange对象，在该对象中封装新的request
        ServerWebExchange userIdExchange = exchange.mutate().request(request).response(exchange.getResponse()).build();

        return chain.filter(userIdExchange);

    }

    /**
     * 务端给客户端返回一个302的状态码，然后响应头中携带location参数，location中的参数就是我们重定向的地址路径
     * @param exchange 封装了请求和响应参数
     * @return
     */
    private Mono<Void> locationUrl(ServerWebExchange exchange) {
        //获取之前的路径参数
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().toString();//获取到的是完整的url路径
        //响应对象
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FOUND);
        response.getHeaders().set("location",authUrlProperties.getLoginpage()+"?originUrl="+uri);

        //解决多次重定向问题：登录成功之后，用户修改了token数据
        // 在重定向到登录页面的时候需要将伪造的token删除掉
        //domain()方法用于获取该cookie的Domain属性，即cookie可以发送到哪些域名下的服务器
        ResponseCookie responseCookie = ResponseCookie.from("token","").domain(".gmall.com").path("/").maxAge(0).build();
        response.addCookie(responseCookie);

        return response.setComplete();
    }

    /**
     * 从redis查询数据，判断token
     * @return
     */
    private UserInfo getUserInfo(String token) {
        String userInfo = redisTemplate.opsForValue().get(GmallConstant.REDIS_USER_LOGIN_TOKEN + token);
        if (StringUtils.isEmpty(userInfo)){
           return null;
        }else {
            return JSON.parseObject(userInfo, UserInfo.class);
        }
    }

    /**
     * 获取token要判断两种方式
     * 第一种：存放在rookie中的token
     * 第二种：存放在请求头中的token
     * 因为普通请求，没有采用前端axios发送，没有经过请求拦截器，请求头中是没有token的，所以要判断cookie中的token
     * @return token值
     */
    private String getToken(ServerWebExchange exchange) {
        //获取请求
        ServerHttpRequest request = exchange.getRequest();
        //从cookie中获取token
        HttpCookie httpCookie = request.getCookies().getFirst("token");
        if (httpCookie!=null){
            //cookie中有token
            return httpCookie.getValue();
        }else {
            //cookie中没有token，从请求头中获取token
            String token = request.getHeaders().getFirst("token");
            if (!StringUtils.isEmpty(token)){
                return token;
            }
        }
        return null;
    }

    /**
     * 给当前自定义的过滤器设置一个int类型的值，这个int类型值后期用来对过滤器进行排序，值越小越靠前，优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}
